﻿#region Using

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Vk.Methods.Common;
using Vk.Methods.News;
using Vk.Methods.Wall;

#endregion

namespace Vk.Methods.Photos
{
    /// <summary>
    ///     Фотографии
    /// </summary>
    public class Photos
    {
        private readonly Api _api;

        public Photos(Api api)
        {
            _api = api;
        }

        /// <summary>
        ///     Подтверждает отметку на фотографии.
        /// </summary>
        /// <param name="photoId">Идентификатор фотографии.</param>
        /// <param name="tagId">Идентификатор отметки на фотографии.</param>
        /// <param name="ownerId">Идентификатор пользователя или сообщества, которому принадлежит фотография.</param>
        public async Task<bool> ConfirmTag(int photoId, int tagId, int? ownerId = null)
        {
            var query = new ApiQuery("photos.confirmTag")
                .Add("photo_id", photoId)
                .Add("tag_id", tagId)
                .Add("owner_id", ownerId);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }

        /// <summary>
        ///     Позволяет скопировать фотографию в альбом "Сохраненные фотографии"
        /// </summary>
        /// <param name="ownerId">Идентификатор владельца фотографии</param>
        /// <param name="photoId">Индентификатор фотографии </param>
        /// <param name="accessKey">Специальный код доступа для приватных фотографий</param>
        /// <returns>Возвращает идентификатор созданной фотографии.</returns>
        public async Task<int> Copy(int ownerId, int photoId, string accessKey = null)
        {
            var query = new ApiQuery("photos.copy")
                .Add("owner_id", ownerId)
                .Add("photo_id", photoId)
                .Add("access_key", accessKey);


            var resp = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed ? resp.Int().Value : -1;
        }

        /// <summary>
        ///     Создает пустой альбом для фотографий.
        /// </summary>
        /// <param name="title">Название альбома.</param>
        /// <param name="privacyView">
        ///     Уровень доступа к альбому.
        /// </param>
        /// <param name="commentsDisabled">Отключено ли комментирование альбома (только для альбома сообщества). </param>
        /// <param name="privacyComment">
        ///     Уровень доступа к комментированию альбома.
        /// </param>
        /// <param name="description">Текст описания альбома.</param>
        /// <param name="groupId">Идентификатор сообщества, в котором создаётся альбом. </param>
        /// <param name="uploadByAdminsOnly">
        ///     Кто может загружать фотографии в альбом (только для альбома сообщества).Возможные значения:
        ///     0 — фотографии могут добавлять все пользователи;
        ///     1 — фотографии могут добавлять только редакторы и администраторы.
        /// </param>
        public async Task<PhotoAlbum> CreateAlbum(string title,
                                                  Privacy privacyView = null,
                                                  int? groupId = null,
                                                  bool? uploadByAdminsOnly = null,
                                                  bool? commentsDisabled = null,
                                                  Privacy privacyComment = null,
                                                  string description = null)
        {
            if (title == null)
                throw new ArgumentNullException(nameof(title));

            var query = new ApiQuery("photos.createAlbum")
                .Add("title", title)
                .Add("privacy", privacyView)
                .Add("comment_privacy", privacyComment)
                .Add("group_id", groupId)
                .Add("upload_by_admins_only", uploadByAdminsOnly)
                .Add("comments_disabled", commentsDisabled)
                .Add("description", description);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed ? new PhotoAlbum(result) : null;
        }

        /// <summary>
        ///     Создает новый комментарий к фотографии.
        /// </summary>
        /// <param name="photoId">Идентификатор фотографии. </param>
        /// <param name="ownerId">Идентификатор пользователя или сообщества, которому принадлежит фотография.</param>
        /// <param name="message">Текст комментария (является обязательным, если не задан параметр attachments). </param>
        /// <param name="replyToComment"></param>
        /// <param name="fromGroup">
        ///     Данный параметр учитывается, если oid менее 0 (комментарий к фотографии группы). true —
        ///     комментарий будет опубликован от имени группы, false — комментарий будет опубликован от имени пользователя (по
        ///     умолчанию).
        /// </param>
        /// <param name="accessKey">Ключ доступа.</param>
        /// <param name="attachments">Cписок объектов, приложенных к комментарию</param>
        /// <param name="stickerId">Идентификатор стикера, который нужно прикрепить к комментарию. </param>
        /// <param name="guid">Уникальное значение для предотвращения повторной отправки одного и того же комментария. </param>
        public async Task<int> CreateComment(int photoId,
                                             int? ownerId,
                                             string message,
                                             int? replyToComment = null,
                                             bool? fromGroup = null,
                                             string accessKey = null,
                                             IEnumerable<WallAttachment> attachments = null,
                                             int? stickerId = null,
                                             string guid = null)
        {
            var query = new ApiQuery("photos.createComment")
                .Add("photo_id", photoId)
                .Add("owner_id", ownerId)
                .Add("message", message)
                .Add("reply_to_comment", replyToComment)
                .Add("access_key", accessKey)
                .Add("from_group", fromGroup)
                .Add("stocker_id", stickerId)
                .Add("guid", guid)
                .Add("attachments", attachments);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed && result.Int().HasValue ? result.Int().Value : -1;
        }

        /// <summary>
        ///     Удаление фотографии на сайте.
        /// </summary>
        /// <param name="ownerId">
        ///     Идентификатор пользователя, которому принадлежит фотография. Если параметр не задан, то считается,
        ///     что он равен идентификатору текущего пользователя. Если передано отрицательное значение, будет удалена фотография
        ///     группы с идентификатором -owner_id.
        /// </param>
        /// <param name="photoId">ID фотографии, которую необходимо удалить.</param>
        /// <returns>Возвращает true в случае успешного удаления фотографии. </returns>
        public async Task<bool> Delete(int ownerId, int photoId)
        {
            var query = new ApiQuery("photos.delete")
                .Add("owner_id", ownerId)
                .Add("photo_id", photoId);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }

        /// <summary>
        ///     Удаляет указанный альбом для фотографий у текущего пользователя.
        /// </summary>
        /// <param name="albumId">Идентификатор удаляемого альбома.</param>
        /// <param name="groupId">Идентификатор группы в том случае, если альбом удаляется из группы.</param>
        public async Task<bool> DeleteAlbum(int albumId, int? groupId = null)
        {
            var query = new ApiQuery("photos.deleteAlbum")
                .Add("album_id", albumId)
                .Add("group_id", groupId);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }

        /// <summary>
        ///     Удаляет комментарий к фотографии.
        /// </summary>
        /// <param name="commentId">Идентификатор комментария.</param>
        /// <param name="ownerId">Идентификатор пользователя (по умолчанию - текущий пользователь).</param>
        /// <returns>
        ///     После успешного выполнения возвращает true (false, если комментарий не найден).
        /// </returns>
        public async Task<bool> DeleteComment(int commentId, int? ownerId = null)
        {
            var query = new ApiQuery("photos.deleteComment")
                .Add("owner_id", ownerId)
                .Add("comment_id", commentId);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }

        /// <summary>
        ///     Редактирует описание или геометку у фотографии.
        /// </summary>
        /// <param name="photoId">ID фотографии, у которой необходимо изменить описание.</param>
        /// <param name="caption">
        ///     Новый текст описания к фотографии. Если параметр не задан, то считается, что он равен пустой
        ///     строке.
        /// </param>
        /// <param name="ownerId">
        ///     Идентификатор пользователя (по умолчанию - текущий пользователь). Если передано отрицательное
        ///     значение, вместо фотографии пользователя будет изменена фотография группы с идентификатором -owner_id.
        /// </param>
        /// <param name="latitude">Географическая широта.</param>
        /// <param name="longitude">Географическая долгота.</param>
        /// <param name="placeStr">Название места.</param>
        /// <param name="foursquareId">id в Foursquare. </param>
        /// <param name="deletePlace">Удалить место (false — не удалять, true — удалить).</param>
        public async Task<bool> Edit(int photoId,
                                     string caption = null,
                                     int? ownerId = null,
                                     double? latitude = null,
                                     double? longitude = null,
                                     string placeStr = null,
                                     string foursquareId = null,
                                     bool? deletePlace = null)
        {
            var query = new ApiQuery("photos.edit")
                .Add("owner_id", ownerId)
                .Add("photo_id", photoId)
                .Add("photo_id", photoId)
                .Add("latitude", latitude)
                .Add("longitude", longitude)
                .Add("place_str", placeStr)
                .Add("foursquare_id", foursquareId)
                .Add("delete_place", deletePlace)
                .Add("caption", caption);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }

        /// <summary>
        ///     Редактирует данные альбома для фотографий пользователя.
        /// </summary>
        /// <param name="albumId">Идентификатор редактируемого альбома.</param>
        /// <param name="title">Новое название альбома.</param>
        /// <param name="privacyView">
        ///     Новый уровень доступа к альбому. Значения: 0 – все пользователи, 1 – только друзья, 2 – друзья и
        ///     друзья друзей, 3 - только я.
        /// </param>
        /// <param name="privacyComment">
        ///     Новый уровень доступа к комментированию альбома. Значения: 0 – все пользователи, 1 – только
        ///     друзья, 2 – друзья и друзья друзей, 3 - только я.
        /// </param>
        /// <param name="description">Новый текст описания альбома.</param>
        /// <param name="ownerId">Идентификатор владельца альбома (пользователь или сообщество).</param>
        /// ///
        /// <param name="uploadByAdminsOnly">
        ///     Кто может загружать фотографии в альбом (только для альбома сообщества).Возможные значения:
        ///     0 — фотографии могут добавлять все пользователи;
        ///     1 — фотографии могут добавлять только редакторы и администраторы.
        /// </param>
        /// <param name="commentsDisabled">Отключено ли комментирование альбома (только для альбома сообщества). </param>
        public async Task<bool> EditAlbum(int albumId,
                                          string title = null,
                                          Privacy privacyView = null,
                                          Privacy privacyComment = null,
                                          bool? uploadByAdminsOnly = null,
                                          bool? commentsDisabled = null,
                                          string description = null,
                                          int? ownerId = null)
        {
            if (title == null)
                throw new ArgumentNullException(nameof(title));

            var query = new ApiQuery("photos.editAlbum")
                .Add("album_id", albumId)
                .Add("title", title)
                .Add("privacy", privacyView)
                .Add("owner_id", ownerId)
                .Add("comment_privacy", privacyComment)
                .Add("upload_by_admins_only", uploadByAdminsOnly)
                .Add("comments_disabled", commentsDisabled)
                .Add("description", description);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }

        /// <summary>
        ///     Изменяет текст комментария к фотографии. Обратите внимание, что редактирование комментария доступно только в
        ///     течение суток после его создания.
        /// </summary>
        /// <param name="commentId">Идентификатор комментария. </param>
        /// <param name="message">Новый текст комментария (является обязательным, если не задан параметр attachments). </param>
        /// <param name="ownerId">Идентификатор пользователя или сообщества, которому принадлежит фотография.</param>
        /// <param name="attachments">Новый список объектов, приложенных к комментарию и разделённых символом ",".</param>
        /// <returns>После успешного выполнения возвращает true.</returns>
        public async Task<bool> EditComment(int commentId,
                                            string message,
                                            int? ownerId = null,
                                            IEnumerable<WallAttachment> attachments = null)
        {
            var query = new ApiQuery("photos.editComment")
                .Add("owner_id", ownerId)
                .Add("message", message)
                .Add("comment_id", commentId)
                .Add("attachments", attachments);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }

        /// <summary>
        ///     Возвращает список фотографий в альбоме.
        /// </summary>
        /// <param name="albumId">Идентификатор альбома</param>
        /// <param name="ownerId">Идентификатор владельца альбома</param>
        /// <param name="photoIds">Идентификаторы фотографий, информацию о которых необходимо вернуть</param>
        /// <param name="rev">Порядок сортировки фотографий (true — антихронологический, false — хронологический). </param>
        /// <param name="extended">
        ///     true — будут возвращены дополнительные поля likes, comments, tags, can_comment. Поля comments и
        ///     tags содержат только количество объектов. По умолчанию данные поля не возвращается.
        /// </param>
        /// <param name="feed">
        ///     Unixtime, который может быть получен методом newsfeed.get в поле date, для получения всех фотографий
        ///     загруженных пользователем в определённый день либо на которых пользователь был отмечен. Также нужно указать
        ///     параметр userId пользователя, с которым произошло событие.
        /// </param>
        /// <param name="feedType">
        ///     Тип новости получаемый в поле type метода newsfeed.get, для получения только загруженных
        ///     пользователем фотографий, либо только фотографий, на которых он был отмечен. Может принимать значения photo,
        ///     photo_tag.
        /// </param>
        /// <param name="photoSizes">Возвращать ли доступные размеры фотографии</param>
        /// <param name="offset">Отступ, необходимый для получения определенного подмножества записей. </param>
        /// <param name="count">Количество записей, которое будет получено. </param>
        public async Task<ListCount<Photo>> Get(int albumId,
                                                int? ownerId = null,
                                                IEnumerable<int> photoIds = null,
                                                bool? rev = null,
                                                bool? extended = null,
                                                int? feed = null,
                                                ApiStringEnum<NewsType> feedType = null,
                                                bool? photoSizes = null,
                                                int? offset = null,
                                                int? count = null)
        {
            var query = new ApiQuery("photos.get", true)
                .Add("owner_id", ownerId)
                .Add("album_id", albumId)
                .Add("photo_ids", photoIds)
                .Add("rev", rev)
                .Add("photo_sizes", photoSizes)
                .Add("extended", extended)
                .Add("feed", feed)
                .Add("feedType", feedType?.Value)
                .Add("offset", offset)
                .Add("count", count);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed
                       ? new ListCount<Photo>(result.Int("count").Value, result.SelectNodes("items/*")
                                                                               .Select(x => new Photo(x))
                                                                               .ToList())
                       : null;
        }

        /// <summary>
        ///     Возвращает список альбомов пользователя или сообщества.
        /// </summary>
        /// <param name="ownerId">Идентификатор пользователя или сообщества, которому принадлежат альбомы.</param>
        /// <param name="albumIds">ID альбомов. </param>
        /// <param name="offset">Смещение, необходимое для выборки определенного подмножества альбомов. </param>
        /// <param name="count">Количество альбомов, которое нужно вернуть. (по умолчанию – все альбомы) </param>
        /// <param name="needCovers">
        ///     true — будет возвращено дополнительное поле thumb_src. По умолчанию поле thumb_src не
        ///     возвращается.
        /// </param>
        /// <param name="needSystem">true – будут возвращены системные альбомы, имеющие отрицательные идентификаторы. </param>
        /// <param name="photoSizes">true — будут возвращены размеры фотографий в специальном формате. </param>
        public async Task<ListCount<PhotoAlbum>> GetAlbums(int? ownerId = null,
                                                           IEnumerable<int> albumIds = null,
                                                           int? offset = null,
                                                           int? count = null,
                                                           bool? needCovers = null,
                                                           bool? needSystem = null,
                                                           bool? photoSizes = null)
        {
            var query = new ApiQuery("photos.getAlbums", true)
                .Add("owner_id", ownerId)
                .Add("album_ids", albumIds)
                .Add("need_covers", needCovers)
                .Add("need_system", needSystem)
                .Add("photo_sizes", photoSizes)
                .Add("offset", offset)
                .Add("count", count);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed
                       ? new ListCount<PhotoAlbum>(result.Int("count").Value,
                                                   result.SelectNodes("items/*").Select(x => new PhotoAlbum(x)).ToList())
                       : null;
        }

        /// <summary>
        ///     Возвращает количество доступных альбомов пользователя.
        /// </summary>
        /// <param name="userId">ID пользователя, которому принадлежат альбомы. По умолчанию – ID текущего пользователя.</param>
        /// <param name="groupId">ID группы, которой принадлежат альбомы.</param>
        public async Task<int> GetAlbumsCount(int? userId = null, int? groupId = null)
        {
            var query = new ApiQuery("photos.getAlbumsCount")
                .Add("user_id", userId)
                .Add("group_id", groupId);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed && result.Int().HasValue ? result.Int().Value : -1;
        }

        /// <summary>
        ///     Возвращает все фотографии пользователя или группы в антихронологическом порядке.
        /// </summary>
        /// <param name="ownerId">Идентификатор пользователя или сообщества, фотографии которого нужно получить.</param>
        /// <param name="count">Количество фотографий, которое необходимо получить (но не более 100).</param>
        /// <param name="photoSizes">true — будут возвращены размеры фотографий в специальном формате.</param>
        /// <param name="offset">Смещение, необходимое для выборки определенного подмножества фотографий.</param>
        /// <param name="extended">true - будет возвращено дополнительное поле likes. По умолчанию поле likes не возвращается.</param>
        /// <param name="noServiceAlbums">
        ///     false - вернуть все фотографии, включая находящиеся в сервисных альбомах, таких как
        ///     "Фотографии на моей стене". (по умолчанию); true - вернуть фотографии только из стандартных альбомов пользователя.
        /// </param>
        /// <param name="needHidden">true — возвращает информацию от том, скрыта ли фотография из блока над стеной пользователя. </param>
        /// <param name="skipHidden">
        ///     true — не возвращать фотографии, скрытые из блока над стеной пользователя (параметр
        ///     учитывается только при owner_id > 0, параметр no_service_albums игнорируется).
        /// </param>
        public async Task<ListCount<Photo>> GetAll(int? ownerId = null,
                                                   int? count = null,
                                                   int? offset = null,
                                                   bool? extended = null,
                                                   bool? noServiceAlbums = null,
                                                   bool? photoSizes = null,
                                                   bool? needHidden = null,
                                                   bool? skipHidden = null)
        {
            var query = new ApiQuery("photos.getAll")
                .Add("owner_id", ownerId)
                .Add("extended", extended)
                .Add("no_service_albums", noServiceAlbums)
                .Add("need_hidden", needHidden)
                .Add("skip_hidden", skipHidden)
                .Add("photo_sizes", photoSizes)
                .Add("count", count)
                .Add("offset", offset);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed
                       ? new ListCount<Photo>(result.Int("count").Value, result.SelectNodes("items/*")
                                                                               .Select(x => new Photo(x))
                                                                               .ToList())
                       : null;
        }

        /// <summary>
        ///     Возвращает отсортированный в антихронологическом порядке список всех комментариев к конкретному альбому или ко всем
        ///     альбомам пользователя.
        /// </summary>
        /// <param name="ownerId">Идентификатор пользователя или сообщества, которому принадлежат фотографии.</param>
        /// <param name="albumId">
        ///     Идентификатор альбома. Если параметр не задан, то считается, что необходимо получить комментарии ко
        ///     всем альбомам пользователя или сообщества.
        /// </param>
        /// <param name="count">
        ///     Количество комментариев, которое необходимо получить. Если параметр не задан, то считается что он
        ///     равен 20. Максимальное значение параметра 100.
        /// </param>
        /// <param name="offset">
        ///     Смещение, необходимое для выборки определенного подмножества комментариев. Если параметр не задан,
        ///     то считается, что он равен 0.
        /// </param>
        /// <param name="needLikes">true — будет возвращено дополнительное поле likes. По умолчанию поле likes не возвращается. </param>
        public async Task<ListCount<PhotoComment>> GetAllComments(int? ownerId = null,
                                                                  int? albumId = null,
                                                                  int? count = null,
                                                                  int? offset = null,
                                                                  bool? needLikes = null)
        {
            var query = new ApiQuery("photos.getAllComments")
                .Add("offset", offset)
                .Add("count", count)
                .Add("owner_id", ownerId)
                .Add("album_id", albumId)
                .Add("need_likes", needLikes);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed
                       ? new ListCount<PhotoComment>(result.Int("count").Value,
                                                     result.SelectNodes("items/*")
                                                           .Select(x => new PhotoComment(x))
                                                           .ToList())
                       : null;
        }

        /// <summary>
        ///     Возвращает информацию о фотографиях по их идентификаторам.
        /// </summary>
        /// <param name="photos">
        ///     перечисленные через запятую идентификаторы, которые представляют собой идущие через знак подчеркивания id
        ///     пользователей, разместивших фотографии, и id самих фотографий. Чтобы получить информацию о фотографии в альбоме
        ///     группы, вместо id пользователя следует указать -id группы.
        ///     Некоторые фотографии, идентификаторы которых могут быть получены через API, закрыты приватностью, и не будут
        ///     получены. В этом случае следует использовать ключ доступа фотографии (access_key) в её идентификаторе.
        /// </param>
        /// <param name="extended">
        ///     true — будут возвращены дополнительные поля likes, comments, tags, can_comment, can_repost. Поля
        ///     comments и tags содержат только количество объектов. По умолчанию данные поля не возвращается.
        /// </param>
        /// <param name="photoSizes">Возвращать ли доступные размеры фотографии в специальном формате. </param>
        /// <returns>После успешного выполнения возвращает список объектов photo. </returns>
        public async Task<List<Photo>> GetById(IEnumerable<string> photos,
                                               bool? extended = null,
                                               bool? photoSizes = null)
        {
            if (photos == null)
                throw new ArgumentNullException(nameof(photos));

            var query = new ApiQuery("photos.getById")
                .Add("photos", photos)
                .Add("extended", extended)
                .Add("photo_sizes", photoSizes);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed
                       ? result.SelectNodes("photo")
                               .Select(x => new Photo(x))
                               .ToList()
                       : null;
        }

        /// <summary>
        ///     Позволяет получить адрес для загрузки фотографий мультидиалогов.
        /// </summary>
        /// <param name="cropX"></param>
        /// <param name="cropY"></param>
        /// <param name="cropWidth">Ширина фотографии после обрезки в px. </param>
        /// <param name="chatId">Идентификатор беседы, для которой нужно загрузить фотографию. </param>
        public async Task<string> GetChatUploadServer(int chatId,
                                                      int? cropX = null,
                                                      int? cropY = null,
                                                      int? cropWidth = null)
        {
            var query = new ApiQuery("photos.getChatUploadServer")
                .Add("chat_id", chatId)
                .Add("crop_x", cropX)
                .Add("crop_y", cropY)
                .Add("crop_width", cropWidth);

            var result = await _api.Manager.Execute(query);
            return !_api.Manager.MethodSuccessed ? null : result.String("upload_url");
        }

        /// <summary>
        ///     Возвращает список комментариев к фотографии.
        /// </summary>
        /// <param name="photoId">Идентификатор фотографии. </param>
        /// <param name="ownerId">Идентификатор пользователя или сообщества, которому принадлежит фотография.</param>
        /// <param name="offset">Смещение, необходимое для выборки определенного подмножества комментариев. По умолчанию — 0. </param>
        /// <param name="count">Количество комментариев, которое необходимо получить. </param>
        /// <param name="sort">Порядок сортировки комментариев (asc — от старых к новым, desc - от новых к старым) </param>
        /// <param name="needLikes">true — будет возвращено дополнительное поле likes. По умолчанию поле likes не возвращается. </param>
        /// <param name="fields">Cписок дополнительных полей профилей, которые необходимо вернуть.</param>
        /// <param name="accessKey">Ключ доступа к фотографии. </param>
        /// <param name="startCommentId">Идентификатор комментария, начиная с которого нужно вернуть список.</param>
        /// <param name="extended">
        ///     true — в ответе будут возвращены дополнительные поля profiles и groups, содержащие информацию о
        ///     пользователях и сообществах. По умолчанию: false.
        /// </param>
        public async Task<ItemsCountProfilesGroups<Comment>> GetComments(int photoId,
                                                                         int? ownerId = null,
                                                                         int? offset = null,
                                                                         int? count = null,
                                                                         SortOrder sort = null,
                                                                         bool? needLikes = null,
                                                                         int? startCommentId = null,
                                                                         bool? extended = null,
                                                                         IEnumerable<string> fields = null,
                                                                         string accessKey = null)
        {
            var query = new ApiQuery("photos.getComments")
                .Add("photo_id", photoId)
                .Add("owner_id", ownerId)
                .Add("offset", offset)
                .Add("count", count)
                .Add("sort", sort.StringValue)
                .Add("access_key", accessKey)
                .Add("start_comment_id", startCommentId)
                .Add("extended", extended)
                .Add("fields", fields)
                .Add("need_likes", needLikes);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed
                       ? new ItemsCountProfilesGroups<Comment>(result, node => new Comment(node))
                       : null;
        }

        /// <summary>
        ///     Возвращает адрес сервера для загрузки фотографии подборки товаров в сообществе.
        ///     После успешной загрузки Вы можете сохранить фотографию, воспользовавшись методом photos.saveMarketAlbumPhoto.
        /// </summary>
        /// <param name="groupId">Идентификатор сообщества, для которого необходимо загрузить фотографию подборки товаров. </param>
        /// <remarks>Минимальный размер фотографии — 1280x720 пикселей.</remarks>
        public async Task<string> GetMarketAlbumUploadServer(int groupId)
        {
            var query = new ApiQuery("photos.getMarketAlbumUploadServer")
                .Add("group_id", groupId);

            var result = await _api.Manager.Execute(query);
            return !_api.Manager.MethodSuccessed ? null : result.String("upload_url");
        }

        /// <summary>
        ///     Возвращает адрес сервера для загрузки фотографии товара.
        ///     После успешной загрузки Вы можете сохранить фотографию, воспользовавшись методом photos.saveMarketPhoto.
        /// </summary>
        /// <param name="groupId">Идентификатор сообщества, для которого необходимо загрузить фотографию товара. </param>
        /// <param name="mainPhoto">
        ///     Является ли фотография обложкой товара (true — фотография для обложки, false — дополнительная
        ///     фотография)
        /// </param>
        /// <param name="cropX">Координата x для обрезки фотографии (верхний правый угол). </param>
        /// <param name="cropY">Координата y для обрезки фотографии (верхний правый угол). </param>
        /// <param name="cropWidth">Ширина фотографии после обрезки в px. </param>
        /// <returns></returns>
        public async Task<string> GetMarketUploadServer(int groupId,
                                                        bool? mainPhoto = null,
                                                        int? cropX = null,
                                                        int? cropY = null,
                                                        int? cropWidth = null)
        {
            var query = new ApiQuery("photos.getMarketUploadServer")
                .Add("group_id", groupId)
                .Add("main_photo", mainPhoto)
                .Add("crop_x", cropX)
                .Add("crop_y", cropY)
                .Add("crop_width", cropWidth);

            var result = await _api.Manager.Execute(query);
            return !_api.Manager.MethodSuccessed ? null : result.String("upload_url");
        }

        /// <summary>
        ///     Возвращает адрес сервера для загрузки﻿ фотографии в личное сообщение пользователю. После успешной загрузки Вы
        ///     можете сохранить фотографию, воспользовавшись методом photos.saveMessagesPhoto.
        /// </summary>
        /// <param name="groupId">Идентификатор сообщества, в альбом которого нужно загрузить фотографию.</param>
        /// <returns>Возвращает объект с единственным полем upload_url. </returns>
        public async Task<string> GetMessagesUploadServer(int? groupId = null)
        {
            var query = new ApiQuery("photos.getMessagesUploadServer")
                .Add("group_id", groupId);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed ? result.String("upload_url") : null;
        }

        /// <summary>
        ///     Возвращает список фотографий на которых есть непросмотренные отметки.
        /// </summary>
        /// <param name="offset">Смещение необходимой для получения определённого подмножества фотографий.</param>
        /// <param name="count">Количество фотографий которые необходимо вернуть.</param>
        public async Task<ListCount<PhotoTagInfo>> GetNewTags(int? offset = null, int? count = null)
        {
            var query = new ApiQuery("photos.getNewTags")
                .Add("offset", offset)
                .Add("count", count);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed
                       ? new ListCount<PhotoTagInfo>(result.Int("count").Value,
                                                     result.SelectNodes("items/*")
                                                           .Select(x => new PhotoTagInfo(x))
                                                           .ToList())
                       : null;
        }

        /// <summary>
        ///     Возвращает адрес сервера для загрузки главной фотографии на страницу пользователя или сообщества.
        /// </summary>
        /// <param name="ownerId">Идентификатор сообщества или текущего пользователя.</param>
        public async Task<string> GetOwnerPhotoUploadServer(int? ownerId = null)
        {
            var query = new ApiQuery("photos.getOwnerPhotoUploadServer")
                .Add("owner_id", ownerId);

            var result = await _api.Manager.Execute(query);
            if (!_api.Manager.MethodSuccessed)
                return null;

            return result.String("upload_url");
        }

        /// <summary>
        ///     Возвращает список фотографий со страницы пользователя или сообщества.
        /// </summary>
        /// <param name="ownerId">Идентификатор пользователя или сообщества, фотографии которого нужно получить.</param>
        /// <param name="albumId">Идентификатор альбома. </param>
        /// <param name="photoIds">Идентификаторы фотографий, информацию о которых необходимо вернуть. </param>
        /// <param name="rev">Порядок сортировки фотографий (true — антихронологический, false — хронологический).</param>
        /// <param name="extended">true — будет возвращено дополнительное поле likes. По умолчанию поле likes не возвращается. </param>
        /// <param name="feedType">
        ///     Тип новости получаемый в поле type метода newsfeed.get, для получения только загруженных
        ///     пользователем фотографий, либо только фотографий, на которых он был отмечен. Может принимать значения photo,
        ///     photo_tag.
        /// </param>
        /// <param name="photoSizes">Возвращать ли доступные размеры фотографии</param>
        public async Task<ListCount<Photo>> GetProfile(int? ownerId = null,
                                                       int? albumId = null,
                                                       IEnumerable<int> photoIds = null,
                                                       bool? rev = null,
                                                       bool? extended = null,
                                                       ApiStringEnum<NewsType> feedType = null,
                                                       bool? photoSizes = null)
        {
            var query = new ApiQuery("photos.getProfile")
                .Add("owner_id", ownerId)
                .Add("album_id", albumId)
                .Add("photo_ids", photoIds)
                .Add("photo_sizes", photoSizes)
                .Add("rev", rev)
                .Add("extended", extended)
                .Add("feedType", feedType.Value);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed
                       ? new ListCount<Photo>(result.Int("count").Value, result.SelectNodes("items/*")
                                                                               .Select(x => new Photo(x))
                                                                               .ToList())
                       : null;
        }

        /// <summary>
        ///     Возвращает список отметок на фотографии.
        /// </summary>
        /// <param name="photoId">Идентификатор фотографии.</param>
        /// <param name="ownerId">Идентификатор пользователя (по умолчанию - текущий пользователь).</param>
        /// <param name="accessKey">Строковой ключ доступа, который может быть получен при получении объекта фотографии. </param>
        public async Task<List<PhotoTag>> GetTags(int photoId, int? ownerId = null, string accessKey = null)
        {
            var query = new ApiQuery("photos.getTags")
                .Add("photo_id", photoId)
                .Add("access_key", accessKey)
                .Add("owner_id", ownerId);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed
                       ? result.ChildNodes.Select(x => new PhotoTag(x)).ToList()
                       : null;
        }

        /// <summary>
        ///     Возвращает адрес сервера для загрузки фотографий. После успешной загрузки фотография может быть сохранена с помощью
        ///     метода photos.save.
        /// </summary>
        /// <param name="albumId">ID альбома, в который необходимо загрузить фотографии.</param>
        /// <param name="groupId">ID группы, при загрузке фотографии в группу.</param>
        public async Task<PhotoUploadInfo> GetUploadServer(int albumId, int? groupId = null)
        {
            var query = new ApiQuery("photos.getUploadServer")
                .Add("album_id", albumId)
                .Add("group_id", groupId);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed ? new PhotoUploadInfo(result) : null;
        }

        /// <summary>
        ///     Возвращает список фотографий, на которых отмечен пользователь.
        /// </summary>
        /// <param name="userId">Идентификатор пользователя (по умолчанию - текущий пользователь).</param>
        /// <param name="offset">Смещение, необходимое для выборки определенного подмножества фотографий.</param>
        /// <param name="count">Количество фотографий, которое необходимо получить (но не более 100).</param>
        /// <param name="sort">
        ///     Сортировка результатов (0 - по дате добавления отметки в порядке убывания, 1 - по дате добавления
        ///     отметки в порядке возрастания).
        /// </param>
        /// <param name="extended">true - будет возвращено дополнительное поле likes. По умолчанию поле likes не возвращается.</param>
        /// <returns>
        ///     Возвращает массив объектов - фотографий, каждый из которых содержит поля pid, aid, owner_id, created, text, src,
        ///     src_big, src_small.
        ///     Первый элемент массива представляет собой общее количество фотографий.
        /// </returns>
        public async Task<ListCount<Photo>> GetUserPhotos(int? userId = null,
                                                          int? offset = null,
                                                          int? count = null,
                                                          SortOrder sort = null,
                                                          bool? extended = null)
        {
            var query = new ApiQuery("photos.getUserPhotos")
                .Add("user_id", userId)
                .Add("offset", offset)
                .Add("count", count)
                .Add("sort", sort.Value)
                .Add("extended", extended);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed
                       ? new ListCount<Photo>(result.Int("count").Value, result.SelectNodes("items/*")
                                                                               .Select(x => new Photo(x))
                                                                               .ToList())
                       : null;
        }

        /// <summary>
        ///     Возвращает адрес сервера для загрузки﻿ фотографии на стену пользователя. После успешной загрузки Вы можете
        ///     сохранить фотографию, воспользовавшись методом photos.saveWallPhoto.
        /// </summary>
        /// <param name="groupId">ID группы, при загрузке фотографии на стену группы.</param>
        public async Task<string> GetWallUploadServer(int? groupId = null)
        {
            var query = new ApiQuery("photos.getWallUploadServer")
                .Add("group_id", groupId);

            var result = await _api.Manager.Execute(query);
            return !_api.Manager.MethodSuccessed ? string.Empty : result.String("upload_url");
        }

        /// <summary>
        ///     Делает фотографию обложкой альбома.
        /// </summary>
        /// <param name="photoId">ID фотографии, которая должна стать обложкой альбома.</param>
        /// <param name="albumId">ID альбома.</param>
        /// <param name="ownerId">ID владельца альбома, по умолчанию id текущего пользователя.</param>
        public async Task<bool> MakeCover(int photoId, int albumId, int? ownerId = null)
        {
            var query = new ApiQuery("photos.makeCover")
                .Add("photo_id", photoId)
                .Add("album_id", albumId)
                .Add("owner_id", ownerId);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }

        /// <summary>
        ///     Переносит фотографию из одного альбома в другой.
        /// </summary>
        /// <param name="photoId">ID переносимой фотографии.</param>
        /// <param name="targetAlbumId">ID альбома, куда переносится фотография.</param>
        /// <param name="ownerId">ID владельца переносимой фотографии, по умолчанию id текущего пользователя.</param>
        public async Task<bool> Move(int photoId, int targetAlbumId, int? ownerId)
        {
            var query = new ApiQuery("photos.move")
                .Add("photo_id", photoId)
                .Add("target_album_id", targetAlbumId)
                .Add("owner_id", ownerId);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }

        /// <summary>
        ///     Добавляет отметку на фотографию.
        /// </summary>
        /// <param name="ownerId">Идентификатор владельца фотографии (по умолчанию - текущий пользователь).</param>
        /// <param name="photoId">Идентификатор фотографии.</param>
        /// <param name="userId">Идентификатор пользователя, которого нужно отметить на фотографии.</param>
        /// <param name="x">Координата верхнего-левого угла отметки в % от ширины фотографии.</param>
        /// <param name="y">Координата верхнего-левого угла отметки в % от высоты фотографии.</param>
        /// <param name="x2">Координата правого-нижнего угла отметки в % от ширины фотографии.</param>
        /// <param name="y2">Координата правого-нижнего угла отметки в % от высоты фотографии.</param>
        /// <returns>Возвращает идентификатор созданной отметки (tag id) </returns>
        public async Task<int> PutTag(int? ownerId, int photoId, int userId, float x, float y, float x2, float y2)
        {
            var query = new ApiQuery("photos.putTag")
                .Add("photo_id", photoId)
                .Add("user_id", userId)
                .Add("x", x)
                .Add("y", y)
                .Add("x2", x2)
                .Add("y2", y2)
                .Add("owner_id", ownerId);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed && result.Int().HasValue ? result.Int().Value : -1;
        }

        /// <summary>
        ///     Удаляет отметку с фотографии.
        /// </summary>
        /// <param name="ownerId">Идентификатор владельца фотографии (по умолчанию - текущий пользователь).</param>
        /// <param name="photoId">Идентификатор фотографии.</param>
        /// <param name="tagId">Идентификатор отметки, которую нужно удалить.</param>
        /// <returns>После успешного выполнения возвращает true.</returns>
        public async Task<bool> RemoveTag(int? ownerId, int photoId, int tagId)
        {
            var query = new ApiQuery("photos.removeTag")
                .Add("photo_id", photoId)
                .Add("tag_id", tagId)
                .Add("owner_id", ownerId);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }

        /// <summary>
        ///     Меняет порядок альбома в списке альбомов пользователя.
        /// </summary>
        /// <param name="albumId">ID альбома, порядок которого нужно изменить.</param>
        /// <param name="before">ID альбома, перед которым следует поместить альбом.</param>
        /// <param name="after">ID альбома, после которого следует поместить альбом.</param>
        /// <param name="ownerId">ID владельца альбома, по умолчанию id текущего пользователя.</param>
        public async Task<bool> ReorderAlbums(int albumId, int before, int after, int? ownerId = null)
        {
            var query = new ApiQuery("photos.reorderAlbums")
                .Add("aid", albumId)
                .Add("before", before)
                .Add("after", after)
                .Add("owner_id", ownerId);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }

        /// <summary>
        ///     Меняет порядок фотографии в списке фотографий альбома пользователя.
        /// </summary>
        /// <param name="photoId">ID фотографии, порядок которой нужно изменить.</param>
        /// <param name="before">ID фотографии, перед которой следует поместить фотографию.</param>
        /// <param name="after">ID фотографии, после которой следует поместить фотографию.</param>
        /// <param name="ownerId">ID владельца фотографии, по умолчанию id текущего пользователя.</param>
        public async Task<bool> ReorderPhotos(int photoId, int before, int after, int? ownerId = null)
        {
            var query = new ApiQuery("photos.reorderPhotos")
                .Add("photo_id", photoId)
                .Add("before", before)
                .Add("after", after)
                .Add("owner_id", ownerId);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }

        /// <summary>
        ///     Позволяет пожаловаться на фотографию.
        /// </summary>
        /// <param name="photoId">Идентификатор фотографии. </param>
        /// <param name="ownerId">Идентификатор пользователя или сообщества, которому принадлежит фотография. </param>
        /// <param name="reason">Тип жалобы</param>
        /// <returns>После успешного выполнения возвращает true.</returns>
        public async Task<bool> Report(int photoId, int ownerId, ReportReason reason = null)
        {
            var query = new ApiQuery("photos.report")
                .Add("owner_id", ownerId)
                .Add("photo_id", photoId)
                .Add("reason", reason?.Value);


            var resp = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed && resp.Bool().Value;
        }

        /// <summary>
        ///     Позволяет пожаловаться на комментарий к фотографии.
        /// </summary>
        /// <param name="commentId">Идентификатор комментария. </param>
        /// <param name="ownerId">Идентификатор владельца фотографии к которой оставлен комментарий.</param>
        /// <param name="reason">Тип жалобы</param>
        /// <returns>После успешного выполнения возвращает true.</returns>
        public async Task<bool> ReportComment(int commentId, int ownerId, ReportReason reason = null)
        {
            var query = new ApiQuery("photos.reportComment")
                .Add("owner_id", ownerId)
                .Add("comment_id", commentId)
                .Add("reason", reason?.Value);


            var resp = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed && resp.Bool().Value;
        }

        /// <summary>
        ///     Восстанавливает удаленную фотографию.
        /// </summary>
        /// <param name="photoId">Идентификатор фотографии. </param>
        /// <param name="ownerId">Идентификатор пользователя или сообщества, которому принадлежит фотография.</param>
        /// <returns></returns>
        public async Task<bool> Restore(int photoId, int? ownerId = null)
        {
            var query = new ApiQuery("photos.restore")
                .Add("owner_id", ownerId)
                .Add("photo_id", photoId);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }

        /// <summary>
        ///     Восстанавливает удаленный комментарий к фотографии.
        /// </summary>
        /// <param name="commentId">Идентификатор комментария.</param>
        /// <param name="ownerId">Идентификатор пользователя (по умолчанию - текущий пользователь).</param>
        /// <returns>
        ///     После успешного выполнения возвращает true (false, если комментарий с таким идентификатором не является удаленным).
        /// </returns>
        public async Task<bool> RestoreComment(int commentId, int? ownerId = null)
        {
            var query = new ApiQuery("photos.restoreComment")
                .Add("owner_id", ownerId)
                .Add("comment_id", commentId);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }

        /// <summary>
        ///     Сохраняет фотографии после успешной загрузки.
        /// </summary>
        /// <param name="albumId">ID альбома, в который необходимо загрузить фотографии.</param>
        /// <param name="server">Параметр, возвращаемый в результате загрузки фотографий на сервер.</param>
        /// <param name="photosList">Параметр, возвращаемый в результате загрузки фотографий на сервер.</param>
        /// <param name="hash">Параметр, возвращаемый в результате загрузки фотографий на сервер.</param>
        /// <param name="groupId">ID группы, при загрузке фотографии в группу.</param>
        /// <param name="caption">Описание фотографии.</param>
        /// <param name="description">Текст описания альбома.</param>
        /// <param name="latitude">Географическая широта, заданная в градусах (от -90 до 90)</param>
        /// <param name="longitude">Географическая долгота, заданная в градусах</param>
        public async Task<Photo> Save(int albumId,
                                      string server,
                                      string photosList,
                                      string hash,
                                      int? groupId,
                                      string caption = null,
                                      string description = null,
                                      double? latitude = null,
                                      double? longitude = null)
        {
            var query = new ApiQuery("photos.save")
                .Add("album_id", albumId)
                .Add("server", server)
                .Add("photos_list", photosList)
                .Add("hash", hash)
                .Add("group_id", groupId)
                .Add("caption", caption)
                .Add("description", description)
                .Add("latitude", latitude)
                .Add("longtitude", longitude);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed ? new Photo(result) : null;
        }

        /// <summary>
        ///     Сохраняет фотографии после успешной загрузки на URI, полученный методом photos.getMarketAlbumUploadServer.
        /// </summary>
        /// <param name="photo">Параметр, возвращаемый в результате загрузки фотографии на сервер. </param>
        /// <param name="groupId">Идентификатор группы, для которой нужно загрузить фотографию. </param>
        /// <param name="server">Параметр, возвращаемый в результате загрузки фотографии на сервер. </param>
        /// <param name="hash">Параметр, возвращаемый в результате загрузки фотографии на сервер. </param>
        public async Task<Photo> SaveMarketAlbumPhoto(string photo,
                                                      int? groupId = null,
                                                      int? server = null,
                                                      string hash = null)
        {
            if (photo == null)
                throw new ArgumentNullException(nameof(photo));

            var query = new ApiQuery("photos.saveMarketAlbumPhoto")
                .Add("photo", photo)
                .Add("server", server)
                .Add("hash", hash)
                .Add("group_id", groupId);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed ? new Photo(result) : null;
        }

        /// <summary>
        ///     Сохраняет фотографии после успешной загрузки на URI, полученный методом photos.getMarketUploadServer.
        /// </summary>
        /// <param name="photo">Параметр, возвращаемый в результате загрузки фотографии на сервер. </param>
        /// <param name="server">Параметр, возвращаемый в результате загрузки фотографии на сервер. </param>
        /// <param name="hash">Параметр, возвращаемый в результате загрузки фотографии на сервер. </param>
        /// <param name="groupId">Идентификатор группы, для которой нужно загрузить фотографию. </param>
        /// <param name="cropData">Параметр, возвращаемый в результате загрузки фотографии на сервер. </param>
        /// <param name="cropHash">Параметр, возвращаемый в результате загрузки фотографии на сервер. </param>
        public async Task<Photo> SaveMarketPhoto(string photo,
                                                 int server,
                                                 string hash,
                                                 int? groupId = null,
                                                 string cropData = null,
                                                 string cropHash = null)
        {
            if (photo == null)
                throw new ArgumentNullException(nameof(photo));
            var query = new ApiQuery("photos.saveMarketAlbumPhoto")
                .Add("photo", photo)
                .Add("server", server)
                .Add("crop_data", cropData)
                .Add("hash", hash)
                .Add("crop_hash", cropHash)
                .Add("group_id", groupId);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed ? new Photo(result) : null;
        }

        /// <summary>
        ///     Сохраняет фотографию после успешной загрузки на URI, полученный методом photos.getMessagesUploadServer.
        /// </summary>
        /// <param name="photo">Параметр, возвращаемый в результате загрузки фотографий на сервер.</param>
        /// <param name="server">Параметр, возвращаемый в результате загрузки фотографии на сервер. </param>
        /// <param name="hash">Параметр, возвращаемый в результате загрузки фотографии на сервер. </param>
        /// <returns>
        ///     Возвращает массив с загруженной фотографией, возвращённый объект имеет поля id, pid, aid, owner_id, src,
        ///     src_big, src_small, created. В случае наличия фотографий в высоком разрешении также будут возвращены адреса с
        ///     названиями src_xbig и src_xxbig.
        /// </returns>
        public async Task<Photo> SaveMessagesPhoto(string photo, int? server = null, string hash = null)
        {
            if (photo == null)
                throw new ArgumentNullException(nameof(photo));

            var query = new ApiQuery("photos.saveMessagesPhoto")
                .Add("photo", photo)
                .Add("server", server)
                .Add("hash", hash);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed ? new Photo(result) : null;
        }

        /// <summary>
        ///     Позволяет сохранить главную фотографию пользователя или сообщества.
        /// </summary>
        /// <param name="server">Параметр, возвращаемый в результате загрузки фотографии на сервер.</param>
        /// <param name="photo">Параметр, возвращаемый в результате загрузки фотографии на сервер.</param>
        /// <param name="hash">Параметр, возвращаемый в результате загрузки фотографии на сервер.</param>
        public async Task<Photo> SaveOwnerPhoto(string server, string photo, string hash)
        {
            var query = new ApiQuery("photos.saveOwnerPhoto")
                .Add("server", server)
                .Add("photo", photo)
                .Add("hash", hash);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed ? new Photo(result) : null;
            //TODO: result type
        }

        /// <summary>
        ///     Сохраняет фотографии после успешной загрузки на URI, полученный методом photos.getWallUploadServer.
        /// </summary>
        /// <param name="photo">Параметр, возвращаемый в результате загрузки фотографии на сервер.</param>
        /// <param name="userId">Идентификатор пользователя, на стену которого нужно сохранить фотографию.</param>
        /// <param name="groupId">Идентификатор сообщества, на стену которого нужно сохранить фотографию.</param>
        /// <param name="server">Параметр, возвращаемый в результате загрузки фотографии на сервер. </param>
        /// <param name="hash">Параметр, возвращаемый в результате загрузки фотографии на сервер. </param>
        public async Task<Photo> SaveWallPhoto(string photo,
                                               int? userId = null,
                                               int? groupId = null,
                                               int? server = null,
                                               string hash = null)
        {
            if (photo == null)
                throw new ArgumentNullException(nameof(photo));

            if (userId != null && groupId != null ||
                userId == null && groupId == null)
                throw new ArgumentException("These parameters can't be null/not null simultaneously");

            var query = new ApiQuery("photos.saveWallPhoto")
                .Add("photo", photo)
                .Add("user_id", userId)
                .Add("server", server)
                .Add("hash", hash)
                .Add("group_id", groupId);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed ? new Photo(result) : null;
        }

        /// <summary>
        ///     Осуществляет поиск изображений по местоположению или описанию.
        /// </summary>
        /// <param name="q">Строка поискового запроса, например, "Nature". </param>
        /// <param name="lat">Географическая широта отметки, заданная в градусах (от -90 до 90). </param>
        /// <param name="long">Географическая долгота отметки, заданная в градусах (от -180 до 180). </param>
        /// <param name="startTime">Время в формате unixtime, не раньше которого должны были быть загружены найденные фотографии. </param>
        /// <param name="endTime">Время в формате unixtime, не позже которого должны были быть загружены найденные фотографии. </param>
        /// <param name="sort">
        ///     true – сортировать по количеству отметок «Мне нравится», false – сортировать по дате добавления
        ///     фотографии.
        /// </param>
        /// <param name="count">Количество возвращаемых фотографий. </param>
        /// <param name="offset">Смещение относительно первой найденной фотографии для выборки определенного подмножества. </param>
        /// <param name="radius">
        ///     Радиус поиска в метрах. (работает очень приближенно, поэтому реальное расстояние до цели может
        ///     отличаться от заданного). Может принимать значения: 10, 100, 800, 6000, 50000
        /// </param>
        public async Task<ListCount<Photo>> Search(string q = null,
                                                   double? lat = null,
                                                   double? @long = null,
                                                   int? startTime = null,
                                                   int? endTime = null,
                                                   bool? sort = null,
                                                   int? count = null,
                                                   int? offset = null,
                                                   int? radius = null)
        {
            var query = new ApiQuery("photos.search")
                .Add("q", q)
                .Add("lat", lat)
                .Add("long", @long)
                .Add("start_time", startTime)
                .Add("end_time", endTime)
                .Add("sort", sort)
                .Add("count", count)
                .Add("offset", offset)
                .Add("radius", radius);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed
                       ? new ListCount<Photo>(result.Int("count").Value, result.SelectNodes("items/*")
                                                                               .Select(x => new Photo(x))
                                                                               .ToList())
                       : null;
        }
    }
}